package com.eage.security.config;

import com.eage.security.ienum.ShiroErrorCode;
import com.eage.security.properties.CompanyProperties;
import com.eage.security.utils.ShiroNetworkUtil;
import com.eage.security.utils.ShiroPublicUtil;
import com.eage.security.utils.ShiroThreadLocalMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
 * 重写shiro获得jsessionId的方法
 * 默认从cookie中获得jsessionId ,现从请求头中"Authorization"获得token
 * 第一步 :自定义sessionId获取类
 *
 * @Author: lex
 * @Date: 2018/11/16
 * 登入解析token 的地方
 */
@Slf4j
public class MySessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    @Autowired
    private StringRedisTemplate redisTemplate;

    private CompanyProperties companyProperties;

    //redisSession前缀,组成是项目名:web模块名 来源是defaultShiroConfig的sessionDao配置
    private String redisSessionPrefix;

    //唯一ip地址前缀,一个ip绑定一个token,防止一个浏览器同时登入2个token,session覆盖问题
    private String uniqueIpAddressPrefix;

    //前端传入token前缀,因为在登入时候默认添加了web模块前缀,所以要删除
    private String tokenPrefix;

    public MySessionManager(CompanyProperties companyProperties) {
        super();
        this.companyProperties = companyProperties;
        //redisSession前缀,组成是项目名:web模块名 来源是defaultShiroConfig的sessionDao配置
        this.redisSessionPrefix = companyProperties.getShiroRedisSession().getName().concat(":").concat(companyProperties.getShiroRedisSession().getUniqueWebProject()).concat(":");

        //唯一ip地址前缀,一个ip绑定一个token,防止一个浏览器同时登入2个token,session覆盖问题
        this.uniqueIpAddressPrefix = companyProperties.getShiroRedisSession().getUniqueIpAddress().concat(":");

        //前端传入token前缀,因为在登入时候默认添加了web模块前缀,所以要删除
        this.tokenPrefix = ("Bearer ").concat(companyProperties.getShiroRedisSession().getUniqueWebProject()).concat(":");
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        sessionId = StringUtils.substringAfter(sessionId, tokenPrefix);
        if (!StringUtils.isEmpty(sessionId)) {
            log.warn("shiro识别jessionId成功 [ {} ]", sessionId);
            //jwt验证 --省略
            sessionId = this.validateRedisSessionId(sessionId, request);
            if (StringUtils.isBlank(sessionId)) {
                return null;
            }
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        } else {
            ShiroThreadLocalMap.put("errorMsg", ShiroErrorCode.CODE_NO_HEAD_TOKEN_ERROR);
            return null;
        }
    }

    /**
     * 1.判断redis中是否有相应的jsessionId,没有就未登入
     * 2.判断当前token是否已经存在ip对应redis中,无论有无,都刷新,如果有的同时,从redis中取出当前ip对应之前的token,如果不一样就删除对应的redisJsessionId
     *
     * @param sessionId
     * @param request
     * @return
     */
    private String validateRedisSessionId(String sessionId, ServletRequest request) {
        //校验session(redis)是否失效
        String redisSessionKey = redisSessionPrefix.concat(sessionId);
        if (redisTemplate.opsForValue().get(redisSessionKey) == null) {
            log.error("登入失败.redis没有相应的key:[ {} ]", redisSessionKey);
            ShiroThreadLocalMap.put("errorMsg", ShiroErrorCode.CODE_NO_REDIS_ERROR);
            return null;
        } else {
            try {
                if (companyProperties.getShiroRedisSession().getUniqueIpAddressEnable()) {
                    //获取当前登入ip ie已经登入,不能再谷歌火狐浏览器登入,更为安全
                    String nowIpAddress = uniqueIpAddressPrefix.concat(ShiroNetworkUtil.getIpAddress((HttpServletRequest) request));
                    //拿到当前ip对应的旧的存在redis中的redisSessionId,判断和当前登入的redisSessionId是否相同,不同就删除,无论相同与否,都要吧当前的RedisSessionId放入里面
                    String oldIpAddressRedisSessionKey = redisTemplate.opsForValue().get(nowIpAddress);
                    //刷新用 的当前新ip对应的token
                    redisTemplate.opsForValue().set(nowIpAddress, redisSessionKey, companyProperties.getShiroRedisSession().getTimeout(), TimeUnit.SECONDS);
                    //如果redis查询到对应的session在判断该浏览器已经有一个token,如果有就删除之前的token,防止token覆盖情况,如token 123对应的user信息是aaa,token对应 345对应bbb,再同一浏览器共同使用可能会发生token 123原本对应aaa改成bbb
                    if (ShiroPublicUtil.isNotEmpty(oldIpAddressRedisSessionKey) && !oldIpAddressRedisSessionKey.equals(redisSessionKey)) {
                        redisTemplate.delete(oldIpAddressRedisSessionKey);
                        log.error("浏览器重复使用sessionId,sessionId冲突已经删除旧为 :[ {} ],新为:[ {} ] ", oldIpAddressRedisSessionKey, redisSessionKey);
                    }
                }
            } catch (IOException e) {
                ShiroThreadLocalMap.put("errorMsg", ShiroErrorCode.CODE_SYSTEM_ERROR);
                return null;
            }
        }
        return sessionId;
    }

}
